package com.ruoyi.platform.aspectj;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.util.StringUtil;
import com.ruoyi.platform.constant.MqConstants;
import com.ruoyi.platform.core.domain.AjaxResult;
import com.ruoyi.platform.core.domain.R;
import com.ruoyi.platform.core.domain.model.LoginUser;
import com.ruoyi.platform.enums.BusinessStatus;
import com.ruoyi.platform.enums.BusinessType;
import com.ruoyi.platform.enums.HttpMethod;
import com.ruoyi.platform.enums.OperatorType;
import com.ruoyi.platform.filter.PropertyPreExcludeFilter;
import com.ruoyi.platform.system.domain.SysOperLog;
import com.ruoyi.platform.utils.*;
import com.ruoyi.platform.utils.ip.AddressUtils;
import com.ruoyi.platform.utils.ip.IpUtils;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Aspect
@Component
public class SysLogAspect
{

    @Value("${saveOperateLog:true}")
    private boolean saveOperateLog;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);

    /** 排除敏感属性字段 */
    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };

    @Around("execution(* *..*Controller.*(..)) && @annotation(io.swagger.annotations.ApiOperation)")
    public Object sysLogs(ProceedingJoinPoint joinPoint) throws Throwable{
        Class<?> targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();

        Method[] methods = targetClass.getMethods();
        // 当前切中的方法
        Method method = null;
        for (int i = 0; i < methods.length; i++){
            if (methods[i].getName() == methodName){
                method = methods[i];
                break;
            }
        }
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);

        String opeName = apiOperation.value();
        if(StringUtil.isEmpty(opeName)){
            opeName = apiOperation.notes();
        }
        ThreadMsgUtil.addMapMsg("sysLogOpeName", opeName);
        // 执行方法前
        Object proceed = null;
        try {
            HttpServletRequest request = HttpUtil.getRequest();
            if(BeanUtils.isNotEmpty(request)) {
                request.setAttribute("enterController", true);
            }
            proceed = joinPoint.proceed();
            // 当前切中的方法

            if(BeanUtils.isEmpty(request)) {
                return proceed;
            }
            // 执行方法后
            //访问目标方法的参数：
            Object[] args = joinPoint.getArgs();
            String reqUrl = request.getRequestURI();
            try {
                if(!saveOperateLog) {
                    logger.error("未开启日志记录");
                    return proceed;
                }
                StringBuffer sb = new StringBuffer();
                sb = getRequestParam(apiOperation.httpMethod(),args, sb);
                String className = joinPoint.getTarget().getClass().getName();
                String executor = "系统[无用户登录系统]";
                // 获取当前的用户
                LoginUser loginUser = null;
                try {
                    loginUser = SecurityUtils.getLoginUser();
                }catch (Exception e){
                    logger.error("获取当前用户信息失败：{}",e.getMessage());
                }
                if(BeanUtils.isNotEmpty(loginUser) && StringUtil.isNotEmpty(loginUser.getUsername())){
                    executor = String.format("%s",loginUser.getUsername());
                }
                // *========数据库日志=========*//
                SysOperLog operLog = new SysOperLog();
                if (BeanUtils.isNotEmpty(proceed) && ( proceed instanceof AjaxResult || proceed instanceof R)){
                    operLog.setJsonResult(JSONObject.toJSONString(proceed));
                }
                operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
                // 请求的地址
                String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
                operLog.setOperIp(ip);
                operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
                if (loginUser != null)
                {
                    operLog.setOperName(executor);
                }
                operLog.setMethod( className+ "." + methodName + "()");
                // 设置请求方式
                operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
                operLog.setBusinessType(wrapperBusinessType(apiOperation));
                // 设置标题
                operLog.setTitle(opeName);
                // 设置操作人类别
                operLog.setOperatorType(OperatorType.MANAGE.ordinal());
                operLog.setOperIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
                operLog.setOperParam(sb.toString());
                operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
                // 保存数据库
                rabbitTemplate.convertAndSend(MqConstants.LOG_EVENT_EXCHANGE,MqConstants.LOG_KEY_NAME, operLog);
//                AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
            } catch (Exception e) {
                logger.error("保存操作日志失败。" + ExceptionUtil.getExceptionMessage(e));
            }

        } catch (Throwable e) {
            throw e;
        }
        return proceed;
    }

    private StringBuffer getRequestParam(String httpMethod ,Object[] args, StringBuffer sb) {
        if(BeanUtils.isNotEmpty(args)){
            for (Object object : args) {
                if(object instanceof ServletRequest || object instanceof ServletResponse) {
                    continue;
                }
                try {
                    String json = JSONObject.toJSONString(object);
                    if ("GET".equals(httpMethod)){
                        sb.append("_"+json);
                    }else{
                        sb.append(json);
                    }
                } catch (Exception e) {
                    sb.append(object.toString());
                }
            }
        }
        return sb;
    }

    private Integer wrapperBusinessType(ApiOperation apiOperation) {
        if ("GET".equals(apiOperation.httpMethod())){
            return BusinessType.OPERATION.ordinal();
        }else if ("POST".equals(apiOperation.httpMethod())){
            return BusinessType.INSERT.ordinal();
        }else if ("PUT".equals(apiOperation.httpMethod())){
            return BusinessType.UPDATE.ordinal();
        }else if ("DELETE".equals(apiOperation.httpMethod())){
            return BusinessType.DELETE.ordinal();
        }
        return BusinessType.OTHER.ordinal();
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(operation)", throwing = "e")
    public void doApiAfterThrowing(JoinPoint joinPoint, ApiOperation operation, Exception e)
    {
        logger.error(e.getLocalizedMessage());
        String opeContent = getErrorDetail(e);
        handleLog(joinPoint, operation, e, opeContent);
    }

    private String getErrorDetail(Exception e) {
        return ExceptionUtils.getMessage(e);
    }

    protected void handleLog(final JoinPoint joinPoint, ApiOperation controllerLog, final Exception e, Object jsonResult)
    {
        try
        {
            // 获取当前的用户
            LoginUser loginUser = SecurityUtils.getLoginUser();

            // *========数据库日志=========*//
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址
            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
            operLog.setOperIp(ip);
            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
            String executor = "系统[无用户登录系统]";
            if(BeanUtils.isNotEmpty(loginUser) && StringUtil.isNotEmpty(loginUser.getUsername())){
                executor = String.format("%s",loginUser.getUsername());
            }
            operLog.setOperName(executor);
            if (e != null)
            {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(String.valueOf(jsonResult), 0, 20000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            StringBuffer builder = new StringBuffer();
            builder = getRequestParam(controllerLog.httpMethod(),joinPoint.getArgs(),builder);
            operLog.setOperParam(builder.toString());
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // 保存数据库
            rabbitTemplate.convertAndSend(MqConstants.LOG_EVENT_EXCHANGE,MqConstants.LOG_KEY_NAME, operLog);
        }
        catch (Exception exp)
        {
            // 记录本地异常日志
            logger.error("==后置通知异常==");
            logger.error("异常信息:{}", exp.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint,ApiOperation  apiOperation, SysOperLog operLog, Object jsonResult) throws Exception
    {
        String opeName = apiOperation.value();
        if(StringUtil.isEmpty(opeName)){
            opeName = apiOperation.notes();
        }
        // 设置action动作
        operLog.setBusinessType(wrapperBusinessType(apiOperation));
        // 设置标题
        operLog.setTitle(opeName);
        // 设置操作人类别
        operLog.setOperatorType(OperatorType.MANAGE.ordinal());
        // 是否需要保存response，参数和值
        if (StringUtils.isNotNull(jsonResult))
        {
            operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
    {
        String requestMethod = operLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
        {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        }
        else
        {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }


    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray)
    {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0)
        {
            for (Object o : paramsArray)
            {
                if (StringUtils.isNotNull(o) && !isFilterObject(o))
                {
                    try
                    {
                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
                        params += jsonObj.toString() + " ";
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o)
    {
        Class<?> clazz = o.getClass();
        if (clazz.isArray())
        {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        }
        else if (Collection.class.isAssignableFrom(clazz))
        {
            Collection collection = (Collection) o;
            for (Object value : collection)
            {
                return value instanceof MultipartFile;
            }
        }
        else if (Map.class.isAssignableFrom(clazz))
        {
            Map map = (Map) o;
            for (Object value : map.entrySet())
            {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

    /**
     * 忽略敏感属性
     */
    public PropertyPreExcludeFilter excludePropertyPreFilter()
    {
        return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
    }

}
